package de.rs.espresso.cubes;

import java.util.List;

import de.rs.espresso.algorithms.model.FreeSet;
import de.rs.espresso.covers.DefaultCover;
import de.rs.espresso.interfaces.ICover;
import de.rs.espresso.interfaces.ICube;
import de.rs.espresso.interfaces.IGlobalInformation;

public class LongCube implements ICube, Cloneable {

	// FIXME: more than one output, also in binary input functions!!

	private IGlobalInformation info;

	private long[] inputs;

	private long[] outputs;

	private int numberOfLiteralsInput;

	private int numberOfLiteralsInLastLongInput;

	private int numberOfLiteralsOutput;

	private int numberOfLiteralsInLastLongOutput;

	private boolean isExpanded = false;

	/**
	 * Creates an Cube completely filled with Don't Cares
	 * 
	 * @param info
	 */
	public LongCube(IGlobalInformation info) {
		this.info = info;

		numberOfLiteralsInput = info.getNumberOfInputs();

		numberOfLiteralsOutput = info.getNumberOfOutputs();

		int numberOfLongsInput = (int) Math.ceil(numberOfLiteralsInput / 32.0);

		int numberOfLongsOutput = (int) Math
				.ceil(numberOfLiteralsOutput / 64.0);

		numberOfLiteralsInLastLongInput = numberOfLiteralsInput % 32;

		numberOfLiteralsInLastLongOutput = numberOfLiteralsOutput % 64;

		if (numberOfLiteralsInLastLongInput == 0 && numberOfLiteralsInput != 0)
			numberOfLiteralsInLastLongInput = 32;

		if (numberOfLiteralsInLastLongOutput == 0
				&& numberOfLiteralsOutput != 0)
			numberOfLiteralsInLastLongOutput = 64;

		inputs = new long[numberOfLongsInput];

		outputs = new long[numberOfLongsOutput];

		for (int i = 0; i < inputs.length; i++) {
			inputs[i] = DONT_CARE_LONG;
		}

		for (int i = 0; i < outputs.length; i++) {
			outputs[i] = DONT_CARE_LONG;
		}

	}

	@Deprecated
	public LongCube(int literalPosition, int numberOfLiteralsInput,
			int numberOfLiteralsOutput, boolean direct) {

		int numberOfLongsInput = (int) Math.ceil(numberOfLiteralsInput / 32.0);

		int numberOfLongsOutput = (int) Math
				.ceil(numberOfLiteralsOutput / 64.0);

		numberOfLiteralsInLastLongInput = numberOfLiteralsInput % 32;

		numberOfLiteralsInLastLongOutput = numberOfLiteralsOutput % 64;

		if (numberOfLiteralsInLastLongInput == 0 && numberOfLiteralsInput != 0)
			numberOfLiteralsInLastLongInput = 32;

		if (numberOfLiteralsInLastLongOutput == 0
				&& numberOfLiteralsOutput != 0)
			numberOfLiteralsInLastLongOutput = 64;

		inputs = new long[numberOfLongsInput];

		outputs = new long[numberOfLongsOutput];

		for (int i = 0; i < inputs.length; i++) {
			inputs[i] = DONT_CARE_LONG;
		}

		for (int i = 0; i < outputs.length; i++) {
			outputs[i] = DONT_CARE_LONG;
		}

		setLiteral(literalPosition, direct ? ICube.DIRECT : ICube.NEGATED);

	}

	public LongCube(List<Byte> inputValues, List<Byte> outputValues,
			IGlobalInformation info) {
		this.info = info;

		numberOfLiteralsInput = inputValues.size();

		numberOfLiteralsOutput = outputValues.size();

		int numberOfLongsInput = (int) Math.ceil(numberOfLiteralsInput / 32.0);

		int numberOfLongsOutput = (int) Math
				.ceil(numberOfLiteralsOutput / 64.0);

		numberOfLiteralsInLastLongInput = numberOfLiteralsInput % 32;

		numberOfLiteralsInLastLongOutput = numberOfLiteralsOutput % 64;

		if (numberOfLiteralsInLastLongInput == 0 && numberOfLiteralsInput != 0)
			numberOfLiteralsInLastLongInput = 32;

		if (numberOfLiteralsInLastLongOutput == 0
				&& numberOfLiteralsOutput != 0)
			numberOfLiteralsInLastLongOutput = 64;

		inputs = new long[numberOfLongsInput];

		int literalCounter = 0;
		int pos = 0;

		for (Byte value : inputValues) {

			inputs[pos] = inputs[pos] | value;

			literalCounter++;

			if (literalCounter == 31) {
				pos++;
				literalCounter = 0;
			} else if (literalCounter + pos * 32 < inputValues.size())
				inputs[pos] = inputs[pos] << 2;

		}

		outputs = new long[numberOfLongsOutput];

		literalCounter = 0;
		pos = 0;

		for (Byte value : outputValues) {

			outputs[pos] = outputs[pos] | value;

			literalCounter++;

			if (literalCounter == 63) {
				pos++;
				literalCounter = 0;
			} else if (literalCounter + pos * 64 < outputValues.size())
				outputs[pos] = outputs[pos] << 1;

		}

	}

	private LongCube(long[] inputs, long[] outputs, int numberOfLiteralsInput,
			int numberOfLiteralsInLastLongInput, int numberOfLiteralsOutput,
			int numberOfLiteralsInLastLongOutput, IGlobalInformation info) {
		this.inputs = inputs;
		this.outputs = outputs;
		this.numberOfLiteralsInput = numberOfLiteralsInput;
		this.numberOfLiteralsOutput = numberOfLiteralsOutput;
		this.numberOfLiteralsInLastLongInput = numberOfLiteralsInLastLongInput;
		this.numberOfLiteralsInLastLongOutput = numberOfLiteralsInLastLongOutput;
		this.info = info;
	}

	@Override
	public int[] addToColumnCount(int[] columnCount) {
		int arrayPosition = 0;
		int j = 1;
		for (long l : inputs) {
			int maxValue = 64;
			if (inputs.length == j) {
				maxValue = 2 * numberOfLiteralsInLastLongInput;
			}
			for (int column = 0; column < maxValue; column++)
				columnCount[arrayPosition + column] = columnCount[arrayPosition
						+ column]
						+ (int) ((l >>> column) & MASK_1_BIT);

			arrayPosition++;
			j++;
		}

		return columnCount;
	}

	@Override
	public int[][] addToDirectNegatedCount(int[][] directNegatedCount) {

		int j = 1;

		for (long l : inputs) {

			int maxValue = 32;
			if ((inputs.length == j)) {
				maxValue = numberOfLiteralsInLastLongInput;
			}

			long copyL = l;

			for (int i = 0; i < maxValue; i++) {
				final long maskedcopyL = copyL & MASK_2_BIT;
				if (maskedcopyL == VARIABLE_DIRECT) {
					directNegatedCount[0][i]++;
				} else if (maskedcopyL == VARIABLE_NEGATED) {
					directNegatedCount[1][i]++;
				}
				copyL = copyL >> 2;
			}

			j++;
		}

		return directNegatedCount;
	}

	@Override
	public ICube and(ICube cube) {
		if (!(cube instanceof LongCube))
			return null;

		LongCube otherCube = (LongCube) cube;

		long[] andInputs = new long[inputs.length];

		for (int j = 0; j < inputs.length; j++) {
			andInputs[j] = inputs[j] & otherCube.inputs[j];
		}

		long[] andOutputs = new long[outputs.length];

		for (int j = 0; j < outputs.length; j++) {
			andOutputs[j] = outputs[j] & otherCube.outputs[j];
		}

		return new LongCube(andInputs, andOutputs, numberOfLiteralsInput,
				numberOfLiteralsInLastLongInput, numberOfLiteralsOutput,
				numberOfLiteralsInLastLongOutput, info);
	}

	@Override
	public LongCube clone() {
		return new LongCube(inputs.clone(), outputs.clone(),
				numberOfLiteralsInput, numberOfLiteralsInLastLongInput,
				numberOfLiteralsOutput, numberOfLiteralsInLastLongOutput, info);
	}

	@Override
	public int countDontCares() {

		int countDontCare = 0;

		int j = 1;

		for (long l : inputs) {

			if ((inputs.length == j)) {

				long copyL = l;

				for (int i = 0; i < numberOfLiteralsInLastLongInput; i++) {
					if ((copyL | MASK_2_BIT_NEGATED) == DONT_CARE_LONG) {
						countDontCare++;
					}
					copyL = copyL >> 2;
				}

			} else {

				long copyL = l;

				for (int i = 0; i < 32; i++) {
					if ((copyL | MASK_2_BIT_NEGATED) == DONT_CARE_LONG) {
						countDontCare++;
					}
					copyL = copyL >> 2;
				}

			}

			j++;
		}

		return countDontCare;
	}

	@Override
	public boolean covers(ICube otherCube) {

		if (!(otherCube instanceof LongCube))
			return false;

		LongCube c = (LongCube) otherCube;

		long mask = DONT_CARE_LONG;
		mask = ~(mask << (numberOfLiteralsInLastLongInput * 2));

		inputs[inputs.length - 1] = inputs[inputs.length - 1] & mask;
		c.inputs[inputs.length - 1] = c.inputs[inputs.length - 1] & mask;

		for (int i = 0; i < this.inputs.length; i++) {
			if (this.inputs[i] != ((this.inputs[i] | c.inputs[i])))
				return false;
		}

		return true;
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof LongCube))
			return false;

		LongCube otherCube = (LongCube) obj;

		if (otherCube.numberOfLiteralsInput != numberOfLiteralsInput)
			return false;

		if (otherCube.numberOfLiteralsInLastLongInput != numberOfLiteralsInLastLongInput)
			return false;

		if (inputs.length > 0) {
			// next 4 lines just for safety. Delete for more performance
			long mask = DONT_CARE_LONG;
			mask = ~(mask << (numberOfLiteralsInLastLongInput * 2));

			inputs[inputs.length - 1] = inputs[inputs.length - 1] & mask;
			otherCube.inputs[inputs.length - 1] = otherCube.inputs[inputs.length - 1]
					& mask;

			int j = 0;

			for (long l : inputs) {
				if (l != otherCube.inputs[j])
					return false;

				j++;
			}
		}

		if (outputs.length > 0) {
			// next 4 lines just for safety. Delete for more performance
			long mask = DONT_CARE_LONG;
			mask = ~(mask << (numberOfLiteralsInLastLongOutput * 2));

			outputs[outputs.length - 1] = outputs[outputs.length - 1] & mask;
			otherCube.outputs[outputs.length - 1] = otherCube.outputs[outputs.length - 1]
					& mask;

			int j = 0;

			for (long l : outputs) {
				if (l != otherCube.outputs[j])
					return false;

				j++;
			}
		}

		return true;

	}

	@Override
	public ICube getCofactor(ICube cube) {

		if (cube.and(this).isVoid()) {
			return null;
		}

		ICube c = this.clone();

		//TODO: for better performance, avoid getLiteral and setLiteral, direct shifting!
		
		for (int i = 0; i < numberOfLiteralsInput; i++) {

			if (cube.getLiteral(i) != ICube.DONT_CARE) {
				c.setLiteral(i, ICube.DONT_CARE);
			}

		}
		
		
		
		for (int i = 0; i < numberOfLiteralsOutput; i++) {

			if (cube.getOutput(i) == ICube.OUTPUT_NOT_PRESENT) {
				c.setOutput(i, ICube.OUTPUT_PRESENT);
			}

		}

		return c;
	}

	@Override
	public ICover getComplement() {
		ICover result = new DefaultCover(info);

		int j = 1;

		for (long l : inputs) {

			int maxValue = 32;
			if ((inputs.length == j)) {
				maxValue = numberOfLiteralsInLastLongInput;
			}

			long copyL = l;

			for (int i = 0; i < maxValue; i++) {
				final long maskedcopyL = copyL & MASK_2_BIT;
				if (maskedcopyL == VARIABLE_DIRECT) {
					LongCube cube = (LongCube) CubeFactory
							.getDontCareCube(info);
					cube.setLiteral(i, NEGATED);
					cube.outputs = outputs.clone();
					result.add(cube);
				} else if (maskedcopyL == VARIABLE_NEGATED) {
					LongCube cube = (LongCube) CubeFactory
							.getDontCareCube(info);
					cube.setLiteral(i, DIRECT);
					cube.outputs = outputs.clone();
					result.add(cube);
				}
				copyL = copyL >> 2;
			}

			j++;
		}

		return result;
	}

	@Override
	public int getCubeWeight(int[] columnCount) {
		int weight = 0;
		int j = 1;
		int arrayPosition = 0;
		for (long l : inputs) {
			int maxValue = 64;
			if(j == inputs.length) maxValue = numberOfLiteralsInLastLongInput;
			for (int column = 0; column < maxValue; column++)
				weight += columnCount[arrayPosition + column]
						* (int) ((l >>> column) & MASK_1_BIT);

			arrayPosition++;
		}

		return weight;

	}

	@Override
	public int getDistance(ICube cube) {

		if (!(cube instanceof LongCube))
			return -1;

		LongCube otherCube = (LongCube) cube;

		int distance = 0;

		for (int count = 1; count <= inputs.length; count++) {

			long intersection = inputs[count-1] & otherCube.inputs[count-1];

			int maxIteration = ((inputs.length == count) ? numberOfLiteralsInLastLongInput
					: 32);

			for (int i = 0; i < maxIteration; i++) {

				if ((intersection & MASK_2_BIT) == 0)
					distance++;

				intersection = intersection >> 2;
			}

		}
		

		
		for (int count = 1; count <= outputs.length; count++) {

			long intersection = outputs[count-1] & otherCube.outputs[count-1];

			int maxIteration = ((outputs.length == count) ? numberOfLiteralsInLastLongOutput
					: 64);

			for (int i = 0; i < maxIteration; i++) {

				if ((intersection & MASK_1_BIT) == 1) {
					return distance;
				}

				
					

				intersection = intersection >> 1;
			}

		}

		return ++distance;
	}

	@Override
	public FreeSet getFreeSet() {
		return new FreeSet(this);
	}

	public ICube getIntersection(ICube otherCube) {
		return and(otherCube);
	}

	@Override
	public byte getLiteral(int position) {

		int arrayPosition = position >>> 5;

		long shift = 2 * (position & MASK_5_BIT);

		// long result =

		return (byte) ((inputs[arrayPosition] >> shift) & MASK_2_BIT);
	}

	@Override
	public int getNumberOfLiterals() {
		return numberOfLiteralsInput;
	}

	@Override
	public int getNumberOfOutputs() {
		return numberOfLiteralsOutput;
	}

	@Override
	public byte getOutput(int position) {

		int arrayPosition = position >>> 6;

		long shift = position & MASK_6_BIT;
		
		byte result = (byte) ((outputs[arrayPosition] >> shift) & MASK_1_BIT);

		return result ;
	}

	@Override
	public ICube getOverexpandedCube(FreeSet fSet) {
		return fSet.getOverexpandedCube();
	}

	@Override
	public ICube getSupercube(ICube otherCube) {
		return or(otherCube);
	}

	@Override
	public int hashCode() {
		int hash = 1;

		hash = 31 * hash + numberOfLiteralsInput;
		hash = 31 * hash + numberOfLiteralsInLastLongInput;

		hash = 31 * hash + numberOfLiteralsOutput;
		hash = 31 * hash + numberOfLiteralsInLastLongOutput;

		for (long l : inputs) {
			hash = 31 * hash + (int) (l ^ (l >>> 32));
		}

		for (long l : outputs) {
			hash = 31 * hash + (int) (l ^ (l >>> 32));
		}

		return hash;

	}

	@Override
	public boolean intersectAt(ICube otherCube, int position) {

		ICube andCube = and(otherCube);
		
		boolean isvoid = andCube.isVoidAt(position);

		return !(and(otherCube).isVoidAt(position));
	}

	@Override
	public boolean isPartSet(int position) {

		// Determine correct long in array.
		int arrayPosition = position >>> 6;
		
		long shift = (position & MASK_6_BIT);

		return ((inputs[arrayPosition] & (1 << shift)) != 0);
	}

	@Override
	public boolean isTautology() {

		int j = 1;

		for (long l : inputs) {

			if ((inputs.length == j)) {

				long mask = DONT_CARE_LONG;
				mask = mask << (numberOfLiteralsInLastLongInput * 2);

				if (((l | mask) ^ DONT_CARE_LONG) != 0) {
					return false;
				}

			} else if ((l ^ DONT_CARE_LONG) != 0)
				return false;

			j++;
		}

		return true;

	}

	@Override
	public boolean isVoid() {
		long[] clonedLiterals = this.inputs;

		int j = 1;

		for (long l : clonedLiterals) {

			int maxIteration = ((clonedLiterals.length == j) ? numberOfLiteralsInLastLongInput
					: 32);

			for (int iteration = 0; iteration < maxIteration; iteration++) {
				if ((l & MASK_2_BIT) == 0) {
					return true;
				} else {
					l = l >> 2;
				}
			}

			j++;
		}

		return false;
	}

	@Override
	public boolean isVoidAt(int position) {

		int arrayPosition = position >>> 6;
		
		long shift = 2 * (position & MASK_6_BIT);
		
		long result = inputs[arrayPosition] & (3 << (shift));
		
		return (result == 0);
	}

	@Override
	public void lowerPart(int position) {

		int arrayPosition = position >>> 6;

		//FIXME: falsch!
		inputs[arrayPosition] = inputs[arrayPosition]
				& ~(1 << (position & (MASK_NEG_6_BIT)));

	}

	@Override
	public ICube or(ICube cube) {
		if (!(cube instanceof LongCube))
			return null;

		LongCube otherCube = (LongCube) cube;

		long[] orInputs = new long[inputs.length];

		for (int j = 0; j < inputs.length; j++) {
			orInputs[j] = inputs[j] | otherCube.inputs[j];
		}

		long[] orOutputs = new long[outputs.length];

		for (int j = 0; j < outputs.length; j++) {
			orOutputs[j] = outputs[j] | otherCube.outputs[j];
		}

		return new LongCube(orInputs, orOutputs, numberOfLiteralsInput,
				numberOfLiteralsInLastLongInput, numberOfLiteralsOutput,
				numberOfLiteralsInLastLongOutput, info);

	}

	@Override
	public void raisePart(int position) {

		int arrayPosition = position >>> 6;
		
		long shift = (position & MASK_6_BIT);

		inputs[arrayPosition] = inputs[arrayPosition]
				| (1 << (shift));

	}

	@Override
	public void setLiteral(int position, byte value) {
		int arrayPosition = position >>> 5;

		long shift = 2 * (position & MASK_5_BIT);

		long mask = ~(3 << shift);

		inputs[arrayPosition] = inputs[arrayPosition] & mask;

		long longValue = value << shift;

		inputs[arrayPosition] = inputs[arrayPosition] | longValue;

	}

	@Override
	public void setOutput(int position, byte value) {
		int arrayPosition = position >>> 6;

		long shift = position & MASK_6_BIT;

		long mask = ~(1 << shift);

		outputs[arrayPosition] = outputs[arrayPosition] & mask;

		long longValue = value << shift;

		outputs[arrayPosition] = outputs[arrayPosition] | longValue;

	}

	@Override
	public String toString() {
		String result = "[";

		for (int i = 0; i < numberOfLiteralsInput; i++) {
			switch (getLiteral(i)) {
			case DIRECT:
				result = result + " 01";
				break;
			case NEGATED:
				result = result + " 10";
				break;
			case DONT_CARE:
				result = result + " 11";
				break;
			case VOID:
				result += " 00";
				break;

			}

			if (result.equals("VOID CUBE"))
				break;

		}

		result = result + " | ";


		for (int i = 0; i < numberOfLiteralsOutput; i++) {
			switch (getOutput(i)) {
			case OUTPUT_PRESENT:
				result = result + " 1";
				break;
			case OUTPUT_NOT_PRESENT:
				result = result + " 0";
				break;
			
			}

		}

		result = result + " ]";

		return result;

	}

	@Override
	public boolean isAlreadyExpanded() {
		return isExpanded ;
	}

	@Override
	public void setAlreadyExpanded(boolean isAlreadyExpanded) {
		isExpanded = isAlreadyExpanded;
	}

	@Override
	public String getBlifString() {
		
		StringBuilder result = new StringBuilder();

		for (int i = 0; i < numberOfLiteralsInput; i++) {
			switch (getLiteral(numberOfLiteralsInput-1-i)) {
			case DIRECT:
				result.append("1");
				break;
			case NEGATED:
				result.append("0");
				break;
			case DONT_CARE:
				result.append("-");
				break;
			case VOID:
				return null;

			}

		}

		result.append(" ");


		for (int i = 0; i < numberOfLiteralsOutput; i++) {
			switch (getOutput(numberOfLiteralsOutput-1-i)) {
			case OUTPUT_PRESENT:
				result.append("1");
				break;
			case OUTPUT_NOT_PRESENT:
				result.append("0");
				break;
			}

		}
		return result.toString();
	}
}
